package com.zjl.redis.第16章_分布式锁;

import cn.hutool.core.util.IdUtil;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.ScriptOutputType;
import io.lettuce.core.SetArgs;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
import org.junit.Test;

import java.math.BigDecimal;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 运行顺序
 *      模拟1
 *      模拟分布式加锁2和3
 *      模拟分布式加锁2和3
 *      模拟4
 *
 *
 */
public class B3_分布式锁_lua_可重入锁 {

    public static RedisCommands<String, String> getRedisCommands() {
        //构建 配置
        RedisURI redis2 = RedisURI.builder()
                .redis("82.157.71.243", 6379)//设置 ip 端口
                .withHost("82.157.71.243")//设置 ip
                .withPort(6379)//设置  端口
                .withAuthentication("default", "er485g96er8")//设置 用户名密码
                .withPassword("er485g96er8")//设置 密码
                .build();

        //连接 reids
        RedisClient redisClient = RedisClient.create(redis2);
        StatefulRedisConnection<String, String> connect = redisClient.connect();
        //创建 操作类
        RedisCommands<String, String> sync = connect.sync();//同步的连接
        return sync;
    }

    private static String AMT = "amt";
    private static String LOCK = "LOCK";
    private static int num = 100;
    private static int fbsNum = 2;

    //过期时间   目的是防止 获取锁的java线程突然挂了，导致别的java程序一直获取不到  px：毫秒  ex：秒
    private static SetArgs setParams = new SetArgs().nx();//.ex(50);

    public static void main(String[] args) throws InterruptedException {
        System.out.println(Thread.currentThread().getName());
        System.out.println(Thread.currentThread().getId());

        ExecutorService threadPool = new ThreadPoolExecutor(10, 10, 2L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10000));
        RedisCommands<String, String> lock = getRedisCommands();
        AtomicInteger atomicInteger = new AtomicInteger(0);//目的是 线程啥时候停止,没有其他目的

        for (int i = 0; i < 100; i++) {
            int finalI = i;
            threadPool.execute(()->{
                try {
                    加锁(lock);
                    加锁(lock);
                    atomicInteger.incrementAndGet();

                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }finally {
                    释放锁(lock);
                    释放锁(lock);
                }
            });
        }
        while (atomicInteger.get() < 100) {
            System.out.println(atomicInteger.get());
            TimeUnit.SECONDS.sleep(1);
        }
        threadPool.shutdown();
        System.out.println("执行完");
        return;

    }
    @Test
    public void 模拟1() {
        RedisCommands<String, String> sync = getRedisCommands();

        System.out.println(sync.get(AMT));
        sync.set(AMT, num + "");
//        sync.set(LOCK, "xxx");
    }

    @Test
    public void 模拟4() {
        RedisCommands<String, String> sync = getRedisCommands();
        System.out.println(sync.del(LOCK));
    }

    @Test //运行 fbsNum （两）次  一般不让运行两次 可以  idea中
    // 鼠标右键-> Modify Run Configuration -> Modify options -> Allow multiple instances  打上对号即可
    public void 模拟分布式加锁2和3() throws InterruptedException {

        //模拟每个java线程
        ExecutorService threadPool = new ThreadPoolExecutor(10, 10, 2L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10000));
        RedisCommands<String, String> lock = getRedisCommands();
        RedisCommands<String, String> redis = getRedisCommands();

        AtomicInteger atomicInteger = new AtomicInteger(0);//目的是 线程啥时候停止
        for (int i = 0; i < num / fbsNum; i++) {
            threadPool.execute(() -> {
                try {

                    加锁(lock);

                    atomicInteger.incrementAndGet();

                    //模拟  分布式下的数据更新的   分布式安全
                    BigDecimal bigDecimal = new BigDecimal(redis.get(AMT));
                    BigDecimal subtract = bigDecimal.subtract(BigDecimal.valueOf(1));
                    redis.set(AMT, subtract.toString());

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {

                    释放锁(lock);

                }
            });
        }
        while (atomicInteger.get() < (num / fbsNum)) {
            System.out.println(atomicInteger.get());
            TimeUnit.SECONDS.sleep(1);
        }
        System.out.println("停止了**************");
        threadPool.shutdown();

    }

    private static void 加锁(RedisCommands<String, String> lock) throws InterruptedException {
        String value = Thread.currentThread().getName() + Thread.currentThread().getId();

        /**
         * 可重入锁
         *  exists key //判断这个key是否存在  1表示true  0表示false
         *  expire key seconds//为键值设置过期时间seconds秒  单位秒
         *  ttl key//查看这个键还有多少秒过期  -1表示永不过期  -2表示已过期
         *
         *  hexists key field//查看key  是否有  field的键
         *  hget key field
         *  hincrby key field increment//给key  的键值对 field对应的值+increment//increment负数就为减
         *  hsetnx key field value//当field不存在时  才添加
         *
         */
        //lua 脚本原子操作
        //KEYS[1] KEYS[2]相当于  eval 方法的 的  LOCK, value
        //当不存在 KEYS[1] 这个 key 或 存在 LOCK 这个 key 的 这个 field
        //则 将 KEYS[1] 这个 key 的 这个 KEYS[2] 值 +1  （没有 相当于 LOCK 这个 key 的 这个 field 值 赋值为 1 ）
        //设置这个 KEYS[1] 这个 key过期时间   500 秒
        String lua = """
           if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],KEYS[2]) == 1 then
                redis.call('hincrby',KEYS[1],KEYS[2],'1')
                redis.call('expire',KEYS[1],500)
                return 1
           else
                return 0
           end
                """;
        //ex 100 秒
        boolean b = true;
        do {
            if(!b){TimeUnit.MICROSECONDS.sleep(50);}
            b = lock.eval(
                    lua,//脚本
                    ScriptOutputType.BOOLEAN,//返回值类型
                    LOCK, value);
        }while(!b);
        System.out.println(value + "加锁：" + b);

    }

    private static void 释放锁(RedisCommands<String, String> lock) {
        /**
         * Lua是一种轻量小巧的脚本语言，用标准C语言编写并以源代码形式开放，
         * 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
         *
         * 设计目的
         *      其设计目的是为了嵌入应用程序中，从而为应用程序提供灵活的扩展和定制功能。
         * Lua特性
         *      ●轻量级它用标准C语言编写并以源代码形式开放.编译后仅仅一百余K,可以很方便的嵌入别的程序里.
         *      ●可扩展Lua提供了非常易于使用的扩展接口和机制: 由宿主语言(通常是C或C++)提供这些功能，
         *              Lua可以使用它们，就像是本来就内置的功能一样。
         * if                     then
         *
         * elseif             then
         *
         * else
         *
         * end
         *
         */

        //lua 脚本原子操作
        // //KEYS[1] KEYS[2]相当于  eval 方法的 的  LOCK, value
        // 如果存在  key 为 KEYS[1] 且  field 为 KEYS[2]
        //         如果  key 为 KEYS[1] 且  field 为 KEYS[2]   值-1 之后为 0 则 删除 key 为 KEYS[1] 的
        //
        String lua = """
           if redis.call('hexists',KEYS[1],KEYS[2]) == 1 then
               if redis.call('hincrby',KEYS[1],KEYS[2],'-1') == 0 then
                    return redis.call('del',KEYS[1])
               else
                    return 1
               end
           else
               return 0
           end
                """;
        String value = Thread.currentThread().getName() + Thread.currentThread().getId();

        boolean eval = lock.eval(
                lua,//脚本
                ScriptOutputType.BOOLEAN,//返回值类型
                LOCK, value);
        System.out.println(value + "释放：" + eval);
    }


}
